/*
 * cache.c
 *
 *  Created on: Nov 19, 2013
 *      Author: adijoshi
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "cache.h"

unsigned int extract_Index(unsigned int address)
{
	return (address << TAG_BITS) >> (ADDRESS_LINES-INDEX_BITS);
}

unsigned int extract_Tag(unsigned int address)
{
	return (address >> (ADDRESS_LINES-TAG_BITS));
}

unsigned int extract_Byte_Offset(unsigned int address)
{
	return (address << (TAG_BITS+INDEX_BITS)) >> (ADDRESS_LINES-BYTE_OFFSET);
}

LineStatus get_Line_Status(int set,int way)
{
	return L2Cache[set][way].MESI_Status;
}

void set_Line_Status(int set,int way,LineStatus nextStatus)
{
	L2Cache[set][way].MESI_Status = nextStatus;
}

int get_Line_Tag(int set,int way)
{
	return L2Cache[set][way].Tag;
}

void set_Line_Tag(int set,int way,int tag)
{
	L2Cache[set][way].Tag = tag;
}

int get_Line_LRU(int set,int way)
{
	return L2Cache[set][way].LRU_Status;
}

void set_Line_LRU(int set,int way,int lruStatus)
{
	L2Cache[set][way].LRU_Status = lruStatus;
}

unsigned int get_Line_Base_Address(unsigned int set,int way)
{
	return (get_Line_Tag(set, way) << (ADDRESS_LINES-TAG_BITS)) +
			(((set)<<(ADDRESS_LINES-INDEX_BITS))>>TAG_BITS);
}

void print_Cache_Stats()
{
	printf("Number of cache hits - %d\n",cacheHit);
	printf("Number of cache misses - %d\n",cacheMiss);
	printf("Number of cache reads - %d\n",cacheRead);
	printf("Number of cache writes - %d\n",cacheWrite);
	printf("Hit ratio - %f\n",(double)cacheHit/(double)(cacheHit+cacheMiss));
	printf("Number of memory reads - %d\n",memRead);
	printf("Number of memory writes - %d\n",memWrite);
}

void clear_Cache_Contents()
{
	int set=0,way=0;

	// Print current contents before clearing all cache
	print_Cache_Stats();

	cacheHit=0;
	cacheMiss=0;
	cacheRead=0;
	cacheWrite=0;
	memRead=0;
	memWrite=0;

	for(set=0; set<SETS; set++)
		for(way=0; way<WAYS; way++)
		{
			set_Line_Status(set,way,Invalid);
			set_Line_LRU(set,way,-1);
			set_Line_Tag(set,way,0);
		}
}

void print_Cache_Contents()
{
	unsigned int set=0;
	int way=0;

	for(set = 0; set < SETS; set++)
		for (way = 0; way < WAYS; way++)
			if (get_Line_Status(set,way) != Invalid)
			{
				printf(" line contains - %x\t %x\t %d\t %d\n",
						//(set*WAYS+way)
						get_Line_Base_Address(set,way),get_Line_Tag(set,way),
						get_Line_Status(set,way),get_Line_LRU(set,way));
			}
	// send to file or not?
}

void read_From_DRAM(unsigned int address)
{
	memRead++;
	print_Output("Read from memory location -", address,0);
}

void write_To_DRAM(unsigned int address)
{
	memWrite++;
	print_Output("Write to memory location -", address,0);
}

void init_Cache()
{
	INDEX_BITS = log2(SETS);
	BYTE_OFFSET = log2(BYTES_PER_LINE);
	TAG_BITS = ADDRESS_LINES-INDEX_BITS-BYTE_OFFSET;

	cacheHit=0;
	cacheMiss=0;
	cacheRead=0;
	cacheWrite=0;
	memRead=0;
	memWrite=0;

	int set=0;
	int way=0;
	for(set=0; set<SETS; set++)
		for(way=0; way<WAYS; way++)
		{
			set_Line_Status(set,way,Invalid);
			set_Line_LRU(set,way,-1);
			set_Line_Tag(set,way,0);
		}
}

void exec_Read_Req(unsigned int accessType, unsigned int address)
{
	int way=-1;
	unsigned int set;
	int snoopResult;
	int printSwitch = OUTPUT_ON_CONSOLE;

	set = extract_Index(address);
	way = match_Tag(set,address);
	//printf("%x - %d\n",address,way);
	if(-1 == way)
	{
		cacheMiss++;
		print_Output("R",address,printSwitch);
		snoopResult = get_Snoop_Result('R',address);
		if(0 == snoopResult)
			read_From_DRAM(address);
		way = fill_Cache_Line(set,address);
		update_LRU(set,way);
		update_MESI(accessType,snoopResult,set,way);
	}
	else // L2 read hit
	{
		cacheHit++;
		update_LRU(set,way);
		// print_output to file
	}
	cacheRead++;
}

void exec_Write_Req(unsigned int accessType, unsigned int address)
{
	int way=-1;
	unsigned int set;
	int snoopResult=0;
	LineStatus oldStatus;
	int printSwitch = OUTPUT_ON_CONSOLE;

	set = extract_Index(address);
	way = match_Tag(set,address);

	if(-1 == way)
	{
		cacheMiss++;
		print_Output("M", address,printSwitch);
		snoopResult = get_Snoop_Result('M',address);
		if(snoopResult == 0)
			read_From_DRAM(address);
		way = fill_Cache_Line(set,address);
		update_LRU(set,way);
		update_MESI(accessType,snoopResult,set,way);
	}
	else // L2 write hit
	{
		cacheHit++;
		oldStatus = get_Line_Status(set,way);
		update_MESI(accessType,snoopResult,set,way);
		if(oldStatus == Shared)
			print_Output("I",address,printSwitch);
		update_LRU(set,way);
		// print_output to file ??
	}
	cacheWrite++;
}

void print_Output(char* operation, unsigned int address, int printSwitch)
{
		if(printSwitch)		//print to console
		{
			printf("%s",operation);
			if(address == -1)
				printf("\n");
			else
				printf(" %x\n",address);
		}
		else				// write to file
		{
			FILE * output_file_ptr;
			output_file_ptr = fopen(OUTPUT_FILE_NAME,"a");

			fprintf(output_file_ptr,"%s",operation);
			if(address == -1)
				fprintf(output_file_ptr,"\n");
			else
				fprintf(output_file_ptr," %x\n",address);

			fflush(output_file_ptr);
			fclose(output_file_ptr);
		}
}



int match_Tag(int set, unsigned int address)
{
	int way = -1;
	int i=0;
	for(i=0;i<WAYS;i++)
		if(get_Line_Tag(set,i) == extract_Tag(address)
				&& (get_Line_Status(set,i) != Invalid))
		{
			way = i;
			break;
		}
	return way;
}



int get_Snoop_Result(char operation, unsigned int address)
{
	if ((address & GET_SNOOP_MASK) == 1 || (address & GET_SNOOP_MASK) == 2)
		return 1;
	else if ((address & GET_SNOOP_MASK) == 3)
		return 2;
	else
		return 0;
}

void put_Snoop_Result(char operation, unsigned int address)
{
	int way=-1;
	unsigned int set;
	int snoopedOperation;
	LineStatus status;
	int printSwitch = OUTPUT_ON_CONSOLE;

	switch(operation){
	case 'R':
		snoopedOperation = SNOOPED_READ;
		break;
	case 'W':
		snoopedOperation = SNOOPED_WRITE;
		break;
	case 'M':
		snoopedOperation = SNOOPED_RFO;
		break;
	case 'I':
		snoopedOperation = SNOOPED_INVALIDATE;
		break;
	}

	set = extract_Index(address);
	way = match_Tag(set,address);

	if (-1 == way)
	{
		print_Output("SR no HIT",-1,printSwitch);
	}
	else
	{
	status = get_Line_Status(set,way);
	switch(status)
		{
			case Modified:
				if((snoopedOperation == SNOOPED_READ) || (snoopedOperation == SNOOPED_RFO))
				{
					print_Output("SR HITM",-1,printSwitch);
					write_To_DRAM(address);
					update_MESI(snoopedOperation,0,set,way);
					if (snoopedOperation == SNOOPED_RFO)
						alert_L1(set,way,'I');
				}
				break;

			case Exclusive:
				if (snoopedOperation == SNOOPED_READ)
				{
					print_Output("SR HIT",-1,printSwitch);
					update_MESI(snoopedOperation,0,set,way);
				}
				else if (snoopedOperation == SNOOPED_RFO)
				{
					alert_L1(set,way,'I');
					update_MESI(snoopedOperation,0,set,way);
				}
				break;

			case Shared:
				if(snoopedOperation == SNOOPED_READ)
				{
					print_Output("SR HIT",-1,printSwitch);
				}
				else if((snoopedOperation == SNOOPED_INVALIDATE) || (snoopedOperation == SNOOPED_RFO))
				{
					alert_L1(set,way,'I');
					update_MESI(snoopedOperation,0,set,way);
				}
				break;

			case Invalid:
				print_Output("SR no HIT",-1,printSwitch);
				break;

			default :
				break;
		}
	}
}

int fill_Cache_Line(int set,unsigned int address)
{
	int way=-1;
	int i=0;
	for(i=0;i<WAYS;i++)
	{
		if(get_Line_Status(set,i) == Invalid)
		{
			way = i;
			break;
		}
	}

	if(-1 == way)
	{
		way = find_LRU_way(set);
		alert_L1(set,way,'E');
		if(get_Line_Status(set,way) == Modified)
			write_To_DRAM(address);
	}

	set_Line_Tag(set,way,extract_Tag(address));
	return way;
}

void update_MESI(unsigned int accessType, int snoopResult, int set, int way)
{
	LineStatus currentStatus = get_Line_Status(set, way);
	LineStatus nextStatus ;
	nextStatus = currentStatus;

	switch (currentStatus) {
	case Invalid:
		if ((accessType == CPU_READ_D || accessType == CPU_READ_I ) &&
				(snoopResult == SNOOPED_HIT || snoopResult == SNOOPED_HITM))
			nextStatus = Shared;
		else if ((accessType == CPU_READ_D || accessType == CPU_READ_I)
				&& snoopResult == SNOOPED_MISS)
			nextStatus = Exclusive;
		else if (accessType == CPU_WRITE_D)
			nextStatus = Modified;
		else
			nextStatus = Invalid;
		break;

	case Modified:
		if (accessType == CPU_READ_D || accessType == CPU_READ_I)
			nextStatus = Modified;
		else if (accessType == CPU_WRITE_D)
			nextStatus = Modified;
		else if (accessType == SNOOPED_RFO)
			nextStatus = Invalid;
		else if (accessType == SNOOPED_READ)
			nextStatus = Shared;
		break;

	case Shared:
		if (accessType == CPU_READ_D || accessType == CPU_READ_I)
			nextStatus = Shared;
		else if (accessType == CPU_WRITE_D)
			nextStatus = Modified;
		else if (accessType == SNOOPED_READ)
			nextStatus = Shared;
		else if (accessType == SNOOPED_RFO || accessType == SNOOPED_INVALIDATE)
			nextStatus = Invalid;
		break;

	case Exclusive:
		if (accessType == CPU_READ_D || accessType == CPU_READ_I)
			nextStatus = Exclusive;
		else if (accessType == CPU_WRITE_D)
			nextStatus = Modified;
		else if (accessType == SNOOPED_RFO)
			nextStatus = Invalid;
		else if (accessType == SNOOPED_READ)
			nextStatus = Shared;
		break;
	}

	set_Line_Status(set,way,nextStatus);
	//if(nextStatus == Invalid)
	//	set_Line_LRU(set,way,-1);
}

void update_LRU(int set, int way)
{
	if(get_Line_LRU(set,way) == -1)
	{
		int i=0;
		for (i = 0; i<WAYS; i++)
			if (get_Line_LRU(set,i)!= -1)
			{
				int currentLRU = get_Line_LRU(set,i);
				set_Line_LRU(set,i,currentLRU+1);
			}
	}
	else
	{
		int i=0;
		for (i = 0; i<WAYS; i++)
			if ((get_Line_LRU(set,i)!= -1) &&
					(get_Line_LRU(set,i) < get_Line_LRU(set,way)))
			{
				int currentLRU = get_Line_LRU(set,i);
				set_Line_LRU(set,i,currentLRU+1);
			}
	}
	set_Line_LRU(set,way,0);
}

int find_LRU_way(int set)
{
	int way;
	for (way = 0; way < WAYS; way++)
	{
		if (get_Line_LRU(set,way) == WAYS-1)
		{
			break;
		}
	}
	return way;
}

void alert_L1(int set,int way, char operation)
{
	unsigned int baseAddress;
	int printSwitch = OUTPUT_ON_CONSOLE;

	baseAddress = get_Line_Base_Address(set,way);
	//baseAddress =(get_Line_Tag(set, way) << (ADDRESS_LINES-TAG_BITS)) + (((set)<<(ADDRESS_LINES-INDEX_BITS))>>TAG_BITS);

	if('E' == operation)
		print_Output("L1 E", baseAddress,printSwitch);
	else if('I' == operation)
		print_Output("L1 I", baseAddress,printSwitch);
}
